///**
// * 
// */
//package org.swing.utility.sytem.jar;
//
//import java.io.ByteArrayInputStream;
//import java.io.ByteArrayOutputStream;
//import java.io.File;
//import java.io.FileInputStream;
//import java.io.FileOutputStream;
//import java.io.IOException;
//import java.io.InputStream;
//import java.io.OutputStream;
//import java.io.OutputStreamWriter;
//import java.io.Writer;
//import java.net.URI;
//import java.net.URISyntaxException;
//import java.net.URL;
//import java.nio.charset.Charset;
//import java.nio.file.FileVisitResult;
//import java.nio.file.Files;
//import java.nio.file.Path;
//import java.nio.file.Paths;
//import java.nio.file.SimpleFileVisitor;
//import java.nio.file.attribute.BasicFileAttributes;
//import java.util.ArrayList;
//import java.util.Collection;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//import java.util.jar.Attributes;
//import java.util.jar.JarEntry;
//import java.util.jar.JarOutputStream;
//import java.util.jar.Manifest;
//import java.util.jar.Pack200;
//import java.util.regex.Pattern;
//import java.util.zip.ZipEntry;
//import java.util.zip.ZipException;
//import java.util.zip.ZipInputStream;
//import static java.nio.charset.StandardCharsets.UTF_8;
//
///**
// * @author lqnhu
// *
// */
//public class Jar {
//	private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; // java.util.jar.JarFile.MANIFEST_NAME
//	private static final String ATTR_MANIFEST_VERSION = "Manifest-Version";
//	private OutputStream os;
//	private final Manifest manifest;
//	private final JarInputStream jis;
//	private JarOutputStream jos;
//	private Pack200.Packer packer;
//	private String jarPrefixStr;
//	private Path jarPrefixFile;
//	private boolean sealed;
//
//	// <editor-fold defaultstate="collapsed" desc="Constructors">
//	// ///////// Constructors ///////////////////////////////////
//	/**
//	 * Creates a new, empty, JAR
//	 */
//	public Jar() {
//		this.jis = null;
//		this.manifest = new Manifest();
//	}
//
//	/**
//	 * Reads in the JAR from the given {@code InputStream}. Modifications will
//	 * not be made to the original JAR file, but to a new copy, which is then
//	 * written with {@link #write(OutputStream) write()}.
//	 */
//	public Jar(InputStream jar) throws IOException {
//		this.jis = jar instanceof JarInputStream ? (JarInputStream) jar
//				: newJarInputStream(jar);
//		this.manifest = new Manifest(jis.getManifest());
//	}
//
//	/**
//	 * Reads in the JAR from the given {@code Path}. Modifications will not be
//	 * made to the original JAR file, but to a new copy, which is then written
//	 * with {@link #write(OutputStream) write()}.
//	 */
//	public Jar(Path jar) throws IOException {
//		this.jis = newJarInputStream(Files.newInputStream(jar));
//		this.manifest = new Manifest(jis.getManifest());
//	}
//
//	/**
//	 * Reads in the JAR from the given {@code File}. Modifications will not be
//	 * made to the original JAR file, but to a new copy, which is then written
//	 * with {@link #write(OutputStream) write()}.
//	 */
//	public Jar(File jar) throws IOException {
//		this(jar.toPath());
//	}
//
//	/**
//	 * Reads in the JAR from the given path. Modifications will not be made to
//	 * the original JAR file, but to a new copy, which is then written with
//	 * {@link #write(OutputStream) write()}.
//	 */
//	public Jar(String jar) throws IOException {
//		this(Paths.get(jar));
//	}
//
//	/**
//	 * Creates a copy
//	 */
//	public Jar(Jar jar) {
//		this.jis = jar.jis;
//		this.manifest = new Manifest(jar.manifest);
//	}
//
//	// </editor-fold>
//	// <editor-fold defaultstate="collapsed" desc="Manifest">
//	// ///////// Manifest ///////////////////////////////////
//	/**
//	 * Returns the manifest of this JAR. Modifications to the manifest will be
//	 * reflected in the written JAR, provided they are done before any entries
//	 * are added with {@code addEntry()}.
//	 */
//	public Manifest getManifest() {
//		return manifest;
//	}
//
//	/**
//	 * Sets an attribute in the main section of the manifest.
//	 *
//	 * @param name
//	 *            the attribute's name
//	 * @param value
//	 *            the attribute's value
//	 * @return {@code this}
//	 * @throws IllegalStateException
//	 *             if entries have been added or the JAR has been written prior
//	 *             to calling this methods.
//	 */
//	public final Jar setAttribute(String name, String value) {
//		verifyNotSealed();
//		if (jos != null)
//			throw new IllegalStateException(
//					"Manifest cannot be modified after entries are added.");
//		getManifest().getMainAttributes().putValue(name, value);
//		return this;
//	}
//
//	/**
//	 * Sets an attribute in a non-main section of the manifest.
//	 *
//	 * @param section
//	 *            the section's name
//	 * @param name
//	 *            the attribute's name
//	 * @param value
//	 *            the attribute's value
//	 * @return {@code this}
//	 * @throws IllegalStateException
//	 *             if entries have been added or the JAR has been written prior
//	 *             to calling this methods.
//	 */
//	public final Jar setAttribute(String section, String name, String value) {
//		verifyNotSealed();
//		if (jos != null)
//			throw new IllegalStateException(
//					"Manifest cannot be modified after entries are added.");
//		Attributes attr = getManifest().getAttributes(section);
//		if (attr == null) {
//			attr = new Attributes();
//			getManifest().getEntries().put(section, attr);
//		}
//		attr.putValue(name, value);
//		return this;
//	}
//
//	/**
//	 * Sets an attribute in the main section of the manifest to a list. The list
//	 * elements will be joined with a single whitespace character.
//	 *
//	 * @param name
//	 *            the attribute's name
//	 * @param values
//	 *            the attribute's value
//	 * @return {@code this}
//	 * @throws IllegalStateException
//	 *             if entries have been added or the JAR has been written prior
//	 *             to calling this methods.
//	 */
//	public Jar setListAttribute(String name, Collection<?> values) {
//		return setAttribute(name, join(values));
//	}
//
//	/**
//	 * Sets an attribute in a non-main section of the manifest to a list. The
//	 * list elements will be joined with a single whitespace character.
//	 *
//	 * @param section
//	 *            the section's name
//	 * @param name
//	 *            the attribute's name
//	 * @param values
//	 *            the attribute's value
//	 * @return {@code this}
//	 * @throws IllegalStateException
//	 *             if entries have been added or the JAR has been written prior
//	 *             to calling this methods.
//	 */
//	public Jar setListAttribute(String section, String name,
//			Collection<?> values) {
//		return setAttribute(section, name, join(values));
//	}
//
//	/**
//	 * Sets an attribute in the main section of the manifest to a map. The map
//	 * entries will be joined with a single whitespace character, and each
//	 * key-value pair will be joined with a '='.
//	 *
//	 * @param name
//	 *            the attribute's name
//	 * @param values
//	 *            the attribute's value
//	 * @return {@code this}
//	 * @throws IllegalStateException
//	 *             if entries have been added or the JAR has been written prior
//	 *             to calling this methods.
//	 */
//	public Jar setMapAttribute(String name, Map<String, ?> values) {
//		return setAttribute(name, join(values));
//	}
//
//	/**
//	 * Sets an attribute in a non-main section of the manifest to a map. The map
//	 * entries will be joined with a single whitespace character, and each
//	 * key-value pair will be joined with a '='.
//	 *
//	 * @param section
//	 *            the section's name
//	 * @param name
//	 *            the attribute's name
//	 * @param values
//	 *            the attribute's value
//	 * @return {@code this}
//	 * @throws IllegalStateException
//	 *             if entries have been added or the JAR has been written prior
//	 *             to calling this methods.
//	 */
//	public Jar setMapAttribute(String section, String name,
//			Map<String, ?> values) {
//		return setAttribute(section, name, join(values));
//	}
//
//	/**
//	 * Returns an attribute's value from this JAR's manifest's main section.
//	 *
//	 * @param name
//	 *            the attribute's name
//	 */
//	public String getAttribute(String name) {
//		return getManifest().getMainAttributes().getValue(name);
//	}
//
//	/**
//	 * Returns an attribute's value from a non-main section of this JAR's
//	 * manifest.
//	 *
//	 * @param section
//	 *            the manifest's section
//	 * @param name
//	 *            the attribute's name
//	 */
//	public String getAttribute(String section, String name) {
//		Attributes attr = getManifest().getAttributes(section);
//		return attr != null ? attr.getValue(name) : null;
//	}
//
//	/**
//	 * Returns an attribute's list value from this JAR's manifest's main
//	 * section. The attributes string value will be split on whitespace into the
//	 * returned list. The returned list may be safely modified.
//	 *
//	 * @param name
//	 *            the attribute's name
//	 */
//	public List<String> getListAttribute(String name) {
//		return split(getAttribute(name));
//	}
//
//	/**
//	 * Returns an attribute's list value from a non-main section of this JAR's
//	 * manifest. The attributes string value will be split on whitespace into
//	 * the returned list. The returned list may be safely modified.
//	 *
//	 * @param section
//	 *            the manifest's section
//	 * @param name
//	 *            the attribute's name
//	 */
//	public List<String> getListAttribute(String section, String name) {
//		return split(getAttribute(section, name));
//	}
//
//	/**
//	 * Returns an attribute's map value from this JAR's manifest's main section.
//	 * The attributes string value will be split on whitespace into map entries,
//	 * and each entry will be split on '=' to get the key-value pair. The
//	 * returned map may be safely modified.
//	 *
//	 * @param name
//	 *            the attribute's name
//	 */
//	public Map<String, String> getMapAttribute(String name, String defaultValue) {
//		return mapSplit(getAttribute(name), defaultValue);
//	}
//
//	/**
//	 * Returns an attribute's map value from a non-main section of this JAR's
//	 * manifest. The attributes string value will be split on whitespace into
//	 * map entries, and each entry will be split on '=' to get the key-value
//	 * pair. The returned map may be safely modified.
//	 *
//	 * @param section
//	 *            the manifest's section
//	 * @param name
//	 *            the attribute's name
//	 */
//	public Map<String, String> getMapAttribute(String section, String name,
//			String defaultValue) {
//		return mapSplit(getAttribute(section, name), defaultValue);
//	}
//
//	// </editor-fold>
//	// <editor-fold defaultstate="collapsed" desc="Entries">
//	// ///////// Entries ///////////////////////////////////
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param is
//	 *            the entry's content
//	 * @return {@code this}
//	 */
//	public Jar addEntry(String path, InputStream is) throws IOException {
//		beginWriting();
//		addEntry(jos, path, is);
//		return this;
//	}
//
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param content
//	 *            the entry's content
//	 * @return {@code this}
//	 */
//	public Jar addEntry(String path, byte[] content) throws IOException {
//		beginWriting();
//		addEntry(jos, path, content);
//		return this;
//	}
//
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param is
//	 *            the entry's content
//	 * @return {@code this}
//	 */
//	public Jar addEntry(Path path, InputStream is) throws IOException {
//		return addEntry(path != null ? path.toString() : "", is);
//	}
//
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param file
//	 *            the file to add as an entry
//	 * @return {@code this}
//	 */
//	public Jar addEntry(Path path, Path file) throws IOException {
//		return addEntry(path, Files.newInputStream(file));
//	}
//
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param file
//	 *            the path of the file to add as an entry
//	 * @return {@code this}
//	 */
//	public Jar addEntry(Path path, String file) throws IOException {
//		return addEntry(path, Paths.get(file));
//	}
//
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param file
//	 *            the file to add as an entry
//	 * @return {@code this}
//	 */
//	public Jar addEntry(String path, File file) throws IOException {
//		try (FileInputStream fos = new FileInputStream(file)) {
//			return addEntry(path, fos);
//		}
//	}
//
//	/**
//	 * Adds an entry to this JAR.
//	 *
//	 * @param path
//	 *            the entry's path within the JAR
//	 * @param file
//	 *            the path of the file to add as an entry
//	 * @return {@code this}
//	 */
//	public Jar addEntry(String path, String file) throws IOException {
//		try (FileInputStream fos = new FileInputStream(file)) {
//			return addEntry(path, fos);
//		}
//	}
//
//	/**
//	 * Adds a class entry to this JAR.
//	 *
//	 * @param clazz
//	 *            the class to add to the JAR.
//	 * @return {@code this}
//	 */
//	public Jar addClass(Class<?> clazz) throws IOException {
//		final String resource = clazz.getName().replace('.', '/') + ".class";
//		return addEntry(resource,
//				clazz.getClassLoader().getResourceAsStream(resource));
//	}
//
//	/**
//	 * Adds a directory (with all its subdirectories) or the contents of a
//	 * zip/JAR to this JAR.
//	 *
//	 * @param path
//	 *            the path within the JAR where the root of the directory will
//	 *            be placed, or {@code null} for the JAR's root
//	 * @param dirOrZip
//	 *            the directory to add as an entry or a zip/JAR file whose
//	 *            contents will be extracted and added as entries
//	 * @param filter
//	 *            a filter to select particular classes
//	 * @return {@code this}
//	 */
//	public Jar addEntries(Path path, Path dirOrZip, Filter filter)
//			throws IOException {
//		if (Files.isDirectory(dirOrZip))
//			addDir(path, dirOrZip, filter, true);
//		else {
//			try (JarInputStream jis1 = newJarInputStream(Files
//					.newInputStream(dirOrZip))) {
//				addEntries(path, jis1, filter);
//			}
//		}
//		return this;
//	}
//
//	/**
//	 * Adds a directory (with all its subdirectories) or the contents of a
//	 * zip/JAR to this JAR.
//	 *
//	 * @param path
//	 *            the path within the JAR where the root of the directory will
//	 *            be placed, or {@code null} for the JAR's root
//	 * @param dirOrZip
//	 *            the directory to add as an entry or a zip/JAR file whose
//	 *            contents will be extracted and added as entries
//	 * @return {@code this}
//	 */
//	public Jar addEntries(Path path, Path dirOrZip) throws IOException {
//		return addEntries(path, dirOrZip, null);
//	}
//
//	/**
//	 * Adds a directory (with all its subdirectories) or the contents of a
//	 * zip/JAR to this JAR.
//	 *
//	 * @param path
//	 *            the path within the JAR where the root of the directory/zip
//	 *            will be placed, or {@code null} for the JAR's root
//	 * @param dirOrZip
//	 *            the directory to add as an entry or a zip/JAR file whose
//	 *            contents will be extracted and added as entries
//	 * @param filter
//	 *            a filter to select particular classes
//	 * @return {@code this}
//	 */
//	public Jar addEntries(String path, Path dirOrZip, Filter filter)
//			throws IOException {
//		return addEntries(path != null ? Paths.get(path) : null, dirOrZip,
//				filter);
//	}
//
//	/**
//	 * Adds a directory (with all its subdirectories) or the contents of a
//	 * zip/JAR to this JAR.
//	 *
//	 * @param path
//	 *            the path within the JAR where the root of the directory/zip
//	 *            will be placed, or {@code null} for the JAR's root
//	 * @param dirOrZip
//	 *            the directory to add as an entry or a zip/JAR file whose
//	 *            contents will be extracted and added as entries
//	 * @return {@code this}
//	 */
//	public Jar addEntries(String path, Path dirOrZip) throws IOException {
//		return addEntries(path, dirOrZip, null);
//	}
//
//	/**
//	 * Adds the contents of the zip/JAR contained in the given byte array to
//	 * this JAR.
//	 *
//	 * @param path
//	 *            the path within the JAR where the root of the zip will be
//	 *            placed, or {@code null} for the JAR's root
//	 * @param zip
//	 *            the contents of the zip/JAR file
//	 * @return {@code this}
//	 */
//	public Jar addEntries(Path path, ZipInputStream zip) throws IOException {
//		return addEntries(path, zip, null);
//	}
//
//	/**
//	 * Adds the contents of the zip/JAR contained in the given byte array to
//	 * this JAR.
//	 *
//	 * @param path
//	 *            the path within the JAR where the root of the zip will be
//	 *            placed, or {@code null} for the JAR's root
//	 * @param zip
//	 *            the contents of the zip/JAR file
//	 * @param filter
//	 *            a filter to select particular classes
//	 * @return {@code this}
//	 */
//	public Jar addEntries(Path path, ZipInputStream zip, Filter filter)
//			throws IOException {
//		beginWriting();
//		try (ZipInputStream zis = zip) {
//			for (ZipEntry entry; (entry = zis.getNextEntry()) != null;) {
//				final String target = path != null ? path.resolve(
//						entry.getName()).toString() : entry.getName();
//				if (target.equals(MANIFEST_NAME))
//					continue;
//				if (filter == null || filter.filter(target))
//					addEntryNoClose(jos, target, zis);
//			}
//		}
//		return this;
//	}
//
//	/**
//	 * Adds the contents of a Java package to this JAR.
//	 *
//	 * @param clazz
//	 *            A class whose package we wish to add to the JAR.
//	 * @return {@code this}
//	 */
//	public Jar addPackageOf(Class<?> clazz) throws IOException {
//		return addPackageOf(clazz, null);
//	}
//
//	/**
//	 * Adds the contents of a Java package to this JAR.
//	 *
//	 * @param clazz
//	 *            a class whose package we wish to add to the JAR.
//	 * @param filter
//	 *            a filter to select particular classes
//	 * @return {@code this}
//	 */
//	public Jar addPackageOf(Class<?> clazz, Filter filter) throws IOException {
//		try {
//			final String path = clazz.getPackage().getName().replace('.', '/');
//			URL dirURL = clazz.getClassLoader().getResource(path);
//			if (dirURL != null && dirURL.getProtocol().equals("file"))
//				addDir(Paths.get(path), Paths.get(dirURL.toURI()), filter,
//						false);
//			else {
//				if (dirURL == null) // In case of a jar file, we can't actually
//									// find a directory.
//					dirURL = clazz.getClassLoader().getResource(
//							clazz.getName().replace('.', '/') + ".class");
//				if (dirURL.getProtocol().equals("jar")) {
//					final URI jarUri = new URI(dirURL.getPath().substring(0,
//							dirURL.getPath().indexOf('!')));
//					try (JarInputStream jis1 = newJarInputStream(Files
//							.newInputStream(Paths.get(jarUri)))) {
//						for (JarEntry entry; (entry = jis1.getNextJarEntry()) != null;) {
//							try {
//								if (entry.getName().startsWith(path + '/')) {
//									if (filter == null
//											|| filter.filter(entry.getName()))
//										addEntryNoClose(jos, entry.getName(),
//												jis1);
//								}
//							} catch (ZipException e) {
//								if (!e.getMessage().startsWith(
//										"duplicate entry"))
//									throw e;
//							}
//						}
//					}
//				} else
//					throw new AssertionError();
//			}
//			return this;
//		} catch (URISyntaxException e) {
//			throw new AssertionError(e);
//		}
//	}
//
//	private void addDir(final Path path, final Path dir1, final Filter filter,
//			final boolean recursive) throws IOException {
//		final Path dir = dir1.toAbsolutePath();
//		Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
//			@Override
//			public FileVisitResult preVisitDirectory(Path d,
//					BasicFileAttributes attrs) throws IOException {
//				return (recursive || dir.equals(d)) ? FileVisitResult.CONTINUE
//						: FileVisitResult.SKIP_SUBTREE;
//			}
//
//			@Override
//			public FileVisitResult visitFile(Path file,
//					BasicFileAttributes attrs) throws IOException {
//				final Path p = dir.relativize(file.toAbsolutePath());
//				final Path target = path != null ? path.resolve(p.toString())
//						: p;
//				if (!target.toString().equals(MANIFEST_NAME)) {
//					if (filter == null || filter.filter(target.toString()))
//						addEntry(target, file);
//				}
//				return FileVisitResult.CONTINUE;
//			}
//		});
//	}
//
//	private static void addEntry(JarOutputStream jarOut, String path,
//			InputStream is) throws IOException {
//		jarOut.putNextEntry(new JarEntry(path));
//		copy(is, jarOut);
//		jarOut.closeEntry();
//	}
//
//	private static void addEntryNoClose(JarOutputStream jarOut, String path,
//			InputStream is) throws IOException {
//		jarOut.putNextEntry(new JarEntry(path));
//		copy0(is, jarOut);
//		jarOut.closeEntry();
//	}
//
//	private static void addEntry(JarOutputStream jarOut, String path,
//			byte[] data) throws IOException {
//		jarOut.putNextEntry(new JarEntry(path));
//		jarOut.write(data);
//		jarOut.flush();
//		jarOut.closeEntry();
//	}
//
//	// </editor-fold>
//	// <editor-fold defaultstate="collapsed" desc="Jar File Properties">
//	// ///////// Jar File Properties ///////////////////////////////////
//	/**
//	 * Sets a {@link Pack200} packer to use when writing the JAR.
//	 *
//	 * @param packer
//	 * @return {@code this}
//	 */
//	public Jar setPacker(Pack200.Packer packer) {
//		this.packer = packer;
//		return this;
//	}
//
//	/**
//	 * If set to true true, a header will be added to the JAR file when written,
//	 * that will make the JAR an executable file in POSIX environments.
//	 *
//	 * @param value
//	 * @return {@code this}
//	 */
//	public Jar setReallyExecutable(boolean value) {
//		setJarPrefix(value ? "#!/bin/sh\n\nexec java -jar $0 \"$@\"\n" : null);
//		return this;
//	}
//
//	/**
//	 * Sets a string that will be prepended to the JAR file's data.
//	 *
//	 * @param value
//	 *            the prefix, or {@code null} for none.
//	 * @return {@code this}
//	 */
//	public Jar setJarPrefix(String value) {
//		verifyNotSealed();
//		if (jos != null)
//			throw new IllegalStateException(
//					"Really executable cannot be set after entries are added.");
//		if (value != null && jarPrefixFile != null)
//			throw new IllegalStateException("A prefix has already been set ("
//					+ jarPrefixFile + ")");
//		this.jarPrefixStr = value;
//		return this;
//	}
//
//	/**
//	 * Sets a file whose contents will be prepended to the JAR file's data.
//	 *
//	 * @param file
//	 *            the prefix file, or {@code null} for none.
//	 * @return {@code this}
//	 */
//	public Jar setJarPrefix(Path file) {
//		verifyNotSealed();
//		if (jos != null)
//			throw new IllegalStateException(
//					"Really executable cannot be set after entries are added.");
//		if (file != null && jarPrefixStr != null)
//			throw new IllegalStateException("A prefix has already been set ("
//					+ jarPrefixStr + ")");
//		this.jarPrefixFile = file;
//		return this;
//	}
//
//	// </editor-fold>
//	// <editor-fold defaultstate="collapsed" desc="Writing">
//	// ///////// Writing ///////////////////////////////////
//	/**
//	 * Sets an {@link OutputStream} to which the JAR will be written. If used,
//	 * this method must be called before any entries have been added or the JAR
//	 * written. Calling this method prevents this object from using an internal
//	 * buffer to store the JAR, and therefore, none of the other {@code write}
//	 * methods can be called.
//	 *
//	 * @param os
//	 *            the target OutputStream of this JAR.
//	 * @return {@code this}
//	 */
//	public Jar setOutputStream(OutputStream os) {
//		if (os == null)
//			throw new NullPointerException("The OutputStream is null");
//		if (jos != null)
//			throw new IllegalStateException(
//					"Entries have already been added, the JAR has been written or setOutputStream has already been called.");
//		this.os = os;
//		return this;
//	}
//
//	/**
//	 * Same as {@link #setOutputStream(OutputStream)
//	 * setOutputStream(Files.newOutputStream(out))}. If used, this method must
//	 * be called before any entries have been added or the JAR written. Calling
//	 * this method prevents this object from using an internal buffer to store
//	 * the JAR, and therefore, none of the other {@code write} methods can be
//	 * called.
//	 *
//	 * @param out
//	 *            the target file to which this JAR will be written.
//	 * @return {@code this}
//	 */
//	public Jar setOutput(Path out) throws IOException {
//		return setOutputStream(Files.newOutputStream(out));
//	}
//
//	/**
//	 * Same as {@link #setOutputStream(OutputStream) setOutputStream(new
//	 * FileOutputStream(out))}. If used, this method must be called before any
//	 * entries have been added or the JAR written. Calling this method prevents
//	 * this object from using an internal buffer to store the JAR, and
//	 * therefore, none of the other {@code write} methods can be called.
//	 *
//	 * @param out
//	 *            the target file to which this JAR will be written.
//	 * @return {@code this}
//	 */
//	public Jar setOutput(File out) throws IOException {
//		return setOutputStream(new FileOutputStream(out));
//	}
//
//	private void beginWriting() throws IOException {
//		verifyNotSealed();
//		if (jos != null)
//			return;
//		if (os == null)
//			this.os = new ByteArrayOutputStream();
//		writePrefix(os);
//		if (getAttribute(ATTR_MANIFEST_VERSION) == null)
//			setAttribute(ATTR_MANIFEST_VERSION, "1.0");
//		jos = new JarOutputStream(os, manifest);
//		if (jis != null)
//			addEntries(null, jis);
//	}
//
//	private void writePrefix(OutputStream os) throws IOException {
//		if (jarPrefixStr != null) {
//			final Writer out = new OutputStreamWriter(os, UTF_8);
//			out.write(jarPrefixStr);
//			out.flush();
//		} else if (jarPrefixFile != null)
//			Files.copy(jarPrefixFile, os);
//		if (jarPrefixStr != null || jarPrefixFile != null) {
//			os.write('\n');
//			os.flush();
//		}
//	}
//
//	public Jar close() throws IOException {
//		if (sealed)
//			return this;
//		beginWriting();
//		// writeManifest(); - some JDK Jar classes (like JarInputStream) assume
//		// that the manifest must be the first entry
//		jos.close();
//		this.sealed = true;
//		return this;
//	}
//
//	/**
//	 * Writes this JAR to an output stream, and closes the stream.
//	 */
//	public <T extends OutputStream> T write(T os) throws IOException {
//		close();
//		if (!(this.os instanceof ByteArrayOutputStream))
//			throw new IllegalStateException(
//					"Cannot write to another target if setOutputStream has been called");
//		final byte[] content = ((ByteArrayOutputStream) this.os).toByteArray();
//		if (packer != null)
//			packer.pack(new JarInputStream(new ByteArrayInputStream(content)),
//					os);
//		else
//			os.write(content);
//		os.close();
//		return os;
//	}
//
//	private void verifyNotSealed() {
//		if (sealed)
//			throw new IllegalStateException(
//					"This JAR has been sealed (when it was written)");
//	}
//
//	/**
//	 * Writes this JAR to a file.
//	 */
//	public File write(File file) throws IOException {
//		try (FileOutputStream fos = new FileOutputStream(file)) {
//			write(fos);
//			return file;
//		}
//	}
//
//	/**
//	 * Writes this JAR to a file.
//	 */
//	public Path write(Path path) throws IOException {
//		write(Files.newOutputStream(path));
//		return path;
//	}
//
//	/**
//	 * Writes this JAR to a file.
//	 */
//	public void write(String file) throws IOException {
//		write(Paths.get(file));
//	}
//
//	/**
//	 * Returns this JAR file as an array of bytes.
//	 */
//	public byte[] toByteArray() {
//		try {
//			return write(new ByteArrayOutputStream()).toByteArray();
//		} catch (IOException e) {
//			throw new AssertionError();
//		}
//	}
//
//	// </editor-fold>
//	/**
//	 * Turns a {@code String} into an {@code InputStream} containing the
//	 * string's encoded characters.
//	 *
//	 * @param str
//	 *            the string
//	 * @param charset
//	 *            the {@link Charset} to use when encoding the string.
//	 * @return an {@link InputStream} containing the string's encoded
//	 *         characters.
//	 */
//	public static InputStream toInputStream(String str, Charset charset) {
//		return new ByteArrayInputStream(str.getBytes(charset));
//	}
//
//	// <editor-fold defaultstate="collapsed" desc="Filter">
//	// ///////// Filter ///////////////////////////////////
//	public static interface Filter {
//		boolean filter(String entryName);
//	}
//
//	public static Filter matches(String regex) {
//		final Pattern p = Pattern.compile(regex);
//		return new Filter() {
//			@Override
//			public boolean filter(String entryName) {
//				return p.matcher(entryName).matches();
//			}
//		};
//	}
//
//	public static Filter notMatches(String regex) {
//		final Filter f = matches(regex);
//		return new Filter() {
//			@Override
//			public boolean filter(String entryName) {
//				return !f.filter(entryName);
//			}
//		};
//	}
//
//	// </editor-fold>
//	// <editor-fold defaultstate="collapsed" desc="Utils">
//	// ///////// Utils ///////////////////////////////////
//	private static JarInputStream newJarInputStream(InputStream in)
//			throws IOException {
//		return new JarInputStream(in);
//	}
//
//	private static ZipInputStream newZipInputStream(InputStream in)
//			throws IOException {
//		return new ZipInputStream(in);
//	}
//
//	private static void copy(InputStream is, OutputStream os)
//			throws IOException {
//		try {
//			copy0(is, os);
//		} finally {
//			is.close();
//		}
//	}
//
//	private static void copy0(InputStream is, OutputStream os)
//			throws IOException {
//		final byte[] buffer = new byte[1024];
//		for (int bytesRead; (bytesRead = is.read(buffer)) != -1;)
//			os.write(buffer, 0, bytesRead);
//		os.flush();
//	}
//
//	private static String join(Collection<?> list, String separator) {
//		if (list == null)
//			return null;
//		StringBuilder sb = new StringBuilder();
//		for (Object element : list)
//			sb.append(element.toString()).append(separator);
//		if (!list.isEmpty())
//			sb.delete(sb.length() - separator.length(), sb.length());
//		return sb.toString();
//	}
//
//	private static String join(Map<String, ?> map, char kvSeparator,
//			String separator) {
//		if (map == null)
//			return null;
//		StringBuilder sb = new StringBuilder();
//		for (Map.Entry<String, ?> entry : map.entrySet())
//			sb.append(entry.getKey()).append(kvSeparator)
//					.append(entry.getValue().toString()).append(separator);
//		if (!map.isEmpty())
//			sb.delete(sb.length() - separator.length(), sb.length());
//		return sb.toString();
//	}
//
//	private static String join(Collection<?> list) {
//		return join(list, " ");
//	}
//
//	private static String join(Map<String, ?> map) {
//		return join(map, '=', " ");
//	}
//
//	private static List<String> split(String str, String separator) {
//		if (str == null)
//			return null;
//		String[] es = str.split(separator);
//		final List<String> list = new ArrayList<>(es.length);
//		for (String e : es) {
//			e = e.trim();
//			if (!e.isEmpty())
//				list.add(e);
//		}
//		return list;
//	}
//
//	private static List<String> split(String list) {
//		return split(list, " ");
//	}
//
//	private static Map<String, String> split(String map, char kvSeparator,
//			String separator, String defaultValue) {
//		if (map == null)
//			return null;
//		Map<String, String> m = new HashMap<>();
//		for (String entry : split(map, separator)) {
//			final String key = getBefore(entry, kvSeparator);
//			String value = getAfter(entry, kvSeparator);
//			if (value == null) {
//				if (defaultValue != null)
//					value = defaultValue;
//				else
//					throw new IllegalArgumentException("Element " + entry
//							+ " in \"" + map
//							+ "\" is not a key-value entry separated with "
//							+ kvSeparator + " and no default value provided");
//			}
//			m.put(key, value);
//		}
//		return m;
//	}
//
//	private static Map<String, String> mapSplit(String map, String defaultValue) {
//		return split(map, '=', " ", defaultValue);
//	}
//
//	private static String getBefore(String s, char separator) {
//		final int i = s.indexOf(separator);
//		if (i < 0)
//			return s;
//		return s.substring(0, i);
//	}
//
//	private static String getAfter(String s, char separator) {
//		final int i = s.indexOf(separator);
//		if (i < 0)
//			return null;
//		return s.substring(i + 1);
//	}
//
//	private static Path nullPath() {
//		return null;
//	}
//	// </editor-fold>
//}
